
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * AUTO-GENERATED FILE. DO NOT MODIFY.
 */

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
import * as zrUtil from 'zrender/lib/core/util.js';
import RoamController from './RoamController.js';
import * as roamHelper from '../../component/helper/roamHelper.js';
import { onIrrelevantElement } from '../../component/helper/cursorHelper.js';
import * as graphic from '../../util/graphic.js';
import { toggleHoverEmphasis, enableComponentHighDownFeatures, setDefaultStateProxy } from '../../util/states.js';
import geoSourceManager from '../../coord/geo/geoSourceManager.js';
import { getUID } from '../../util/component.js';
import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle.js';
import { getECData } from '../../util/innerStore.js';
import { createOrUpdatePatternFromDecal } from '../../util/decal.js';
import Displayable from 'zrender/lib/graphic/Displayable.js';
import { makeInner } from '../../util/model.js';
/**
 * Only these tags enable use `itemStyle` if they are named in SVG.
 * Other tags like <text> <tspan> <image> might not suitable for `itemStyle`.
 * They will not be considered to be styled until some requirements come.
 */

var OPTION_STYLE_ENABLED_TAGS = ['rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'path'];
var OPTION_STYLE_ENABLED_TAG_MAP = zrUtil.createHashMap(OPTION_STYLE_ENABLED_TAGS);
var STATE_TRIGGER_TAG_MAP = zrUtil.createHashMap(OPTION_STYLE_ENABLED_TAGS.concat(['g']));
var LABEL_HOST_MAP = zrUtil.createHashMap(OPTION_STYLE_ENABLED_TAGS.concat(['g']));
var mapLabelRaw = makeInner();

function getFixedItemStyle(model) {
	var itemStyle = model.getItemStyle();
	var areaColor = model.get('areaColor'); // If user want the color not to be changed when hover,
	// they should both set areaColor and color to be null.

	if (areaColor != null) {
		itemStyle.fill = areaColor;
	}

	return itemStyle;
} // Only stroke can be used for line.
// Using fill in style if stroke not exits.
// TODO Not sure yet. Perhaps a separate `lineStyle`?

function fixLineStyle(styleHost) {
	var style = styleHost.style;

	if (style) {
		style.stroke = style.stroke || style.fill;
		style.fill = null;
	}
}

var MapDraw =
/** @class */
function () {
	function MapDraw(api) {
		var group = new graphic.Group();
		this.uid = getUID('ec_map_draw');
		this._controller = new RoamController(api.getZr());
		this._controllerHost = {
			target: group
		};
		this.group = group;
		group.add(this._regionsGroup = new graphic.Group());
		group.add(this._svgGroup = new graphic.Group());
	}

	MapDraw.prototype.draw = function (mapOrGeoModel, ecModel, api, fromView, payload) {
		var isGeo = mapOrGeoModel.mainType === 'geo'; // Map series has data. GEO model that controlled by map series
		// will be assigned with map data. Other GEO model has no data.

		var data = mapOrGeoModel.getData && mapOrGeoModel.getData();
		isGeo && ecModel.eachComponent({
			mainType: 'series',
			subType: 'map'
		}, function (mapSeries) {
			if (!data && mapSeries.getHostGeoModel() === mapOrGeoModel) {
				data = mapSeries.getData();
			}
		});
		var geo = mapOrGeoModel.coordinateSystem;
		var regionsGroup = this._regionsGroup;
		var group = this.group;
		var transformInfo = geo.getTransformInfo();
		var transformInfoRaw = transformInfo.raw;
		var transformInfoRoam = transformInfo.roam; // No animation when first draw or in action

		var isFirstDraw = !regionsGroup.childAt(0) || payload;

		if (isFirstDraw) {
			group.x = transformInfoRoam.x;
			group.y = transformInfoRoam.y;
			group.scaleX = transformInfoRoam.scaleX;
			group.scaleY = transformInfoRoam.scaleY;
			group.dirty();
		} else {
			graphic.updateProps(group, transformInfoRoam, mapOrGeoModel);
		}

		var isVisualEncodedByVisualMap = data && data.getVisual('visualMeta') && data.getVisual('visualMeta').length > 0;
		var viewBuildCtx = {
			api: api,
			geo: geo,
			mapOrGeoModel: mapOrGeoModel,
			data: data,
			isVisualEncodedByVisualMap: isVisualEncodedByVisualMap,
			isGeo: isGeo,
			transformInfoRaw: transformInfoRaw
		};

		if (geo.resourceType === 'geoJSON') {
			this._buildGeoJSON(viewBuildCtx);
		} else if (geo.resourceType === 'geoSVG') {
			this._buildSVG(viewBuildCtx);
		}

		this._updateController(mapOrGeoModel, ecModel, api);

		this._updateMapSelectHandler(mapOrGeoModel, regionsGroup, api, fromView);
	};

	MapDraw.prototype._buildGeoJSON = function (viewBuildCtx) {
		var regionsGroupByName = this._regionsGroupByName = zrUtil.createHashMap();
		var regionsInfoByName = zrUtil.createHashMap();
		var regionsGroup = this._regionsGroup;
		var transformInfoRaw = viewBuildCtx.transformInfoRaw;
		var mapOrGeoModel = viewBuildCtx.mapOrGeoModel;
		var data = viewBuildCtx.data;
		var projection = viewBuildCtx.geo.projection;
		var projectionStream = projection && projection.stream;

		function transformPoint(point, project) {
			if (project) {
				// projection may return null point.
				point = project(point);
			}

			return point && [point[0] * transformInfoRaw.scaleX + transformInfoRaw.x, point[1] * transformInfoRaw.scaleY + transformInfoRaw.y];
		}

		function transformPolygonPoints(inPoints) {
			var outPoints = []; // If projectionStream is provided. Use it instead of single point project.

			var project = !projectionStream && projection && projection.project;

			for (var i = 0; i < inPoints.length; ++i) {
				var newPt = transformPoint(inPoints[i], project);
				newPt && outPoints.push(newPt);
			}

			return outPoints;
		}

		function getPolyShape(points) {
			return {
				shape: {
					points: transformPolygonPoints(points)
				}
			};
		}

		regionsGroup.removeAll(); // Only when the resource is GeoJSON, there is `geo.regions`.

		zrUtil.each(viewBuildCtx.geo.regions, function (region) {
			var regionName = region.name; // Consider in GeoJson properties.name may be duplicated, for example,
			// there is multiple region named "United Kindom" or "France" (so many
			// colonies). And it is not appropriate to merge them in geo, which
			// will make them share the same label and bring trouble in label
			// location calculation.

			var regionGroup = regionsGroupByName.get(regionName);

			var _a = regionsInfoByName.get(regionName) || {},
				dataIdx = _a.dataIdx,
				regionModel = _a.regionModel;

			if (!regionGroup) {
				regionGroup = regionsGroupByName.set(regionName, new graphic.Group());
				regionsGroup.add(regionGroup);
				dataIdx = data ? data.indexOfName(regionName) : null;
				regionModel = viewBuildCtx.isGeo ? mapOrGeoModel.getRegionModel(regionName) : data ? data.getItemModel(dataIdx) : null;
				regionsInfoByName.set(regionName, {
					dataIdx: dataIdx,
					regionModel: regionModel
				});
			}

			var polygonSubpaths = [];
			var polylineSubpaths = [];
			zrUtil.each(region.geometries, function (geometry) {
				// Polygon and MultiPolygon
				if (geometry.type === 'polygon') {
					var polys = [geometry.exterior].concat(geometry.interiors || []);

					if (projectionStream) {
						polys = projectPolys(polys, projectionStream);
					}

					zrUtil.each(polys, function (poly) {
						polygonSubpaths.push(new graphic.Polygon(getPolyShape(poly)));
					});
				} // LineString and MultiLineString
				else {
					var points = geometry.points;

					if (projectionStream) {
						points = projectPolys(points, projectionStream, true);
					}

					zrUtil.each(points, function (points) {
						polylineSubpaths.push(new graphic.Polyline(getPolyShape(points)));
					});
				}
			});
			var centerPt = transformPoint(region.getCenter(), projection && projection.project);

			function createCompoundPath(subpaths, isLine) {
				if (!subpaths.length) {
					return;
				}

				var compoundPath = new graphic.CompoundPath({
					culling: true,
					segmentIgnoreThreshold: 1,
					shape: {
						paths: subpaths
					}
				});
				regionGroup.add(compoundPath);
				applyOptionStyleForRegion(viewBuildCtx, compoundPath, dataIdx, regionModel);
				resetLabelForRegion(viewBuildCtx, compoundPath, regionName, regionModel, mapOrGeoModel, dataIdx, centerPt);

				if (isLine) {
					fixLineStyle(compoundPath);
					zrUtil.each(compoundPath.states, fixLineStyle);
				}
			}

			createCompoundPath(polygonSubpaths);
			createCompoundPath(polylineSubpaths, true);
		}); // Ensure children have been added to `regionGroup` before calling them.

		regionsGroupByName.each(function (regionGroup, regionName) {
			var _a = regionsInfoByName.get(regionName),
				dataIdx = _a.dataIdx,
				regionModel = _a.regionModel;

			resetEventTriggerForRegion(viewBuildCtx, regionGroup, regionName, regionModel, mapOrGeoModel, dataIdx);
			resetTooltipForRegion(viewBuildCtx, regionGroup, regionName, regionModel, mapOrGeoModel);
			resetStateTriggerForRegion(viewBuildCtx, regionGroup, regionName, regionModel, mapOrGeoModel);
		}, this);
	};

	MapDraw.prototype._buildSVG = function (viewBuildCtx) {
		var mapName = viewBuildCtx.geo.map;
		var transformInfoRaw = viewBuildCtx.transformInfoRaw;
		this._svgGroup.x = transformInfoRaw.x;
		this._svgGroup.y = transformInfoRaw.y;
		this._svgGroup.scaleX = transformInfoRaw.scaleX;
		this._svgGroup.scaleY = transformInfoRaw.scaleY;

		if (this._svgResourceChanged(mapName)) {
			this._freeSVG();

			this._useSVG(mapName);
		}

		var svgDispatcherMap = this._svgDispatcherMap = zrUtil.createHashMap();
		var focusSelf = false;
		zrUtil.each(this._svgGraphicRecord.named, function (namedItem) {
			// Note that we also allow different elements have the same name.
			// For example, a glyph of a city and the label of the city have
			// the same name and their tooltip info can be defined in a single
			// region option.
			var regionName = namedItem.name;
			var mapOrGeoModel = viewBuildCtx.mapOrGeoModel;
			var data = viewBuildCtx.data;
			var svgNodeTagLower = namedItem.svgNodeTagLower;
			var el = namedItem.el;
			var dataIdx = data ? data.indexOfName(regionName) : null;
			var regionModel = mapOrGeoModel.getRegionModel(regionName);

			if (OPTION_STYLE_ENABLED_TAG_MAP.get(svgNodeTagLower) != null && el instanceof Displayable) {
				applyOptionStyleForRegion(viewBuildCtx, el, dataIdx, regionModel);
			}

			if (el instanceof Displayable) {
				el.culling = true;
			} // We do not know how the SVG like so we'd better not to change z2.
			// Otherwise it might bring some unexpected result. For example,
			// an area hovered that make some inner city can not be clicked.

			el.z2EmphasisLift = 0; // If self named:

			if (!namedItem.namedFrom) {
				// label should batter to be displayed based on the center of <g>
				// if it is named rather than displayed on each child.
				if (LABEL_HOST_MAP.get(svgNodeTagLower) != null) {
					resetLabelForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel, dataIdx, null);
				}

				resetEventTriggerForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel, dataIdx);
				resetTooltipForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel);

				if (STATE_TRIGGER_TAG_MAP.get(svgNodeTagLower) != null) {
					var focus_1 = resetStateTriggerForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel);

					if (focus_1 === 'self') {
						focusSelf = true;
					}

					var els = svgDispatcherMap.get(regionName) || svgDispatcherMap.set(regionName, []);
					els.push(el);
				}
			}
		}, this);

		this._enableBlurEntireSVG(focusSelf, viewBuildCtx);
	};

	MapDraw.prototype._enableBlurEntireSVG = function (focusSelf, viewBuildCtx) {
		// It's a little complicated to support blurring the entire geoSVG in series-map.
		// So do not support it until some requirements come.
		// At present, in series-map, only regions can be blurred.
		if (focusSelf && viewBuildCtx.isGeo) {
			var blurStyle = viewBuildCtx.mapOrGeoModel.getModel(['blur', 'itemStyle']).getItemStyle(); // Only support `opacity` here. Because not sure that other props are suitable for
			// all of the elements generated by SVG (especially for Text/TSpan/Image/... ).

			var opacity_1 = blurStyle.opacity;

			this._svgGraphicRecord.root.traverse(function (el) {
				if (!el.isGroup) {
					// PENDING: clear those settings to SVG elements when `_freeSVG`.
					// (Currently it happen not to be needed.)
					setDefaultStateProxy(el);
					var style = el.ensureState('blur').style || {}; // Do not overwrite the region style that already set from region option.

					if (style.opacity == null && opacity_1 != null) {
						style.opacity = opacity_1;
					} // If `ensureState('blur').style = {}`, there will be default opacity.
					// Enable `stateTransition` (animation).

					el.ensureState('emphasis');
				}
			});
		}
	};

	MapDraw.prototype.remove = function () {
		this._regionsGroup.removeAll();

		this._regionsGroupByName = null;

		this._svgGroup.removeAll();

		this._freeSVG();

		this._controller.dispose();

		this._controllerHost = null;
	};

	MapDraw.prototype.findHighDownDispatchers = function (name, geoModel) {
		if (name == null) {
			return [];
		}

		var geo = geoModel.coordinateSystem;

		if (geo.resourceType === 'geoJSON') {
			var regionsGroupByName = this._regionsGroupByName;

			if (regionsGroupByName) {
				var regionGroup = regionsGroupByName.get(name);
				return regionGroup ? [regionGroup] : [];
			}
		} else if (geo.resourceType === 'geoSVG') {
			return this._svgDispatcherMap && this._svgDispatcherMap.get(name) || [];
		}
	};

	MapDraw.prototype._svgResourceChanged = function (mapName) {
		return this._svgMapName !== mapName;
	};

	MapDraw.prototype._useSVG = function (mapName) {
		var resource = geoSourceManager.getGeoResource(mapName);

		if (resource && resource.type === 'geoSVG') {
			var svgGraphic = resource.useGraphic(this.uid);

			this._svgGroup.add(svgGraphic.root);

			this._svgGraphicRecord = svgGraphic;
			this._svgMapName = mapName;
		}
	};

	MapDraw.prototype._freeSVG = function () {
		var mapName = this._svgMapName;

		if (mapName == null) {
			return;
		}

		var resource = geoSourceManager.getGeoResource(mapName);

		if (resource && resource.type === 'geoSVG') {
			resource.freeGraphic(this.uid);
		}

		this._svgGraphicRecord = null;
		this._svgDispatcherMap = null;

		this._svgGroup.removeAll();

		this._svgMapName = null;
	};

	MapDraw.prototype._updateController = function (mapOrGeoModel, ecModel, api) {
		var geo = mapOrGeoModel.coordinateSystem;
		var controller = this._controller;
		var controllerHost = this._controllerHost; // @ts-ignore FIXME:TS

		controllerHost.zoomLimit = mapOrGeoModel.get('scaleLimit');
		controllerHost.zoom = geo.getZoom(); // roamType is will be set default true if it is null
		// @ts-ignore FIXME:TS

		controller.enable(mapOrGeoModel.get('roam') || false);
		var mainType = mapOrGeoModel.mainType;

		function makeActionBase() {
			var action = {
				type: 'geoRoam',
				componentType: mainType
			};
			action[mainType + 'Id'] = mapOrGeoModel.id;
			return action;
		}

		controller.off('pan').on('pan', function (e) {
			this._mouseDownFlag = false;
			roamHelper.updateViewOnPan(controllerHost, e.dx, e.dy);
			api.dispatchAction(zrUtil.extend(makeActionBase(), {
				dx: e.dx,
				dy: e.dy,
				animation: {
					duration: 0
				}
			}));
		}, this);
		controller.off('zoom').on('zoom', function (e) {
			this._mouseDownFlag = false;
			roamHelper.updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY);
			api.dispatchAction(zrUtil.extend(makeActionBase(), {
				zoom: e.scale,
				originX: e.originX,
				originY: e.originY,
				animation: {
					duration: 0
				}
			}));
		}, this);
		controller.setPointerChecker(function (e, x, y) {
			return geo.containPoint([x, y]) && !onIrrelevantElement(e, api, mapOrGeoModel);
		});
	};
	/**
   * FIXME: this is a temporarily workaround.
   * When `geoRoam` the elements need to be reset in `MapView['render']`, because the props like
   * `ignore` might have been modified by `LabelManager`, and `LabelManager#addLabelsOfSeries`
   * will subsequently cache `defaultAttr` like `ignore`. If do not do this reset, the modified
   * props will have no chance to be restored.
   * Note: This reset should be after `clearStates` in `renderSeries` because `useStates` in
   * `renderSeries` will cache the modified `ignore` to `el._normalState`.
   * TODO:
   * Use clone/immutable in `LabelManager`?
   */

	MapDraw.prototype.resetForLabelLayout = function () {
		this.group.traverse(function (el) {
			var label = el.getTextContent();

			if (label) {
				label.ignore = mapLabelRaw(label).ignore;
			}
		});
	};

	MapDraw.prototype._updateMapSelectHandler = function (mapOrGeoModel, regionsGroup, api, fromView) {
		var mapDraw = this;
		regionsGroup.off('mousedown');
		regionsGroup.off('click'); // @ts-ignore FIXME:TS resolve type conflict

		if (mapOrGeoModel.get('selectedMode')) {
			regionsGroup.on('mousedown', function () {
				mapDraw._mouseDownFlag = true;
			});
			regionsGroup.on('click', function (e) {
				if (!mapDraw._mouseDownFlag) {
					return;
				}

				mapDraw._mouseDownFlag = false;
			});
		}
	};

	return MapDraw;
}();

function applyOptionStyleForRegion(viewBuildCtx, el, dataIndex, regionModel) {
	// All of the path are using `itemStyle`, because
	// (1) Some SVG also use fill on polyline (The different between
	// polyline and polygon is "open" or "close" but not fill or not).
	// (2) For the common props like opacity, if some use itemStyle
	// and some use `lineStyle`, it might confuse users.
	// (3) Most SVG use <path>, where can not detect whether to draw a "line"
	// or a filled shape, so use `itemStyle` for <path>.
	var normalStyleModel = regionModel.getModel('itemStyle');
	var emphasisStyleModel = regionModel.getModel(['emphasis', 'itemStyle']);
	var blurStyleModel = regionModel.getModel(['blur', 'itemStyle']);
	var selectStyleModel = regionModel.getModel(['select', 'itemStyle']); // NOTE: DON'T use 'style' in visual when drawing map.
	// This component is used for drawing underlying map for both geo component and map series.

	var normalStyle = getFixedItemStyle(normalStyleModel);
	var emphasisStyle = getFixedItemStyle(emphasisStyleModel);
	var selectStyle = getFixedItemStyle(selectStyleModel);
	var blurStyle = getFixedItemStyle(blurStyleModel); // Update the itemStyle if has data visual

	var data = viewBuildCtx.data;

	if (data) {
		// Only visual color of each item will be used. It can be encoded by visualMap
		// But visual color of series is used in symbol drawing
		// Visual color for each series is for the symbol draw
		var style = data.getItemVisual(dataIndex, 'style');
		var decal = data.getItemVisual(dataIndex, 'decal');

		if (viewBuildCtx.isVisualEncodedByVisualMap && style.fill) {
			normalStyle.fill = style.fill;
		}

		if (decal) {
			normalStyle.decal = createOrUpdatePatternFromDecal(decal, viewBuildCtx.api);
		}
	} // SVG text, tspan and image can be named but not supporeted
	// to be styled by region option yet.

	el.setStyle(normalStyle);
	el.style.strokeNoScale = true;
	el.ensureState('emphasis').style = emphasisStyle;
	el.ensureState('select').style = selectStyle;
	el.ensureState('blur').style = blurStyle; // Enable blur

	setDefaultStateProxy(el);
}

function resetLabelForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel, // Exist only if `viewBuildCtx.data` exists.
	dataIdx, // If labelXY not provided, use `textConfig.position: 'inside'`
	labelXY) {
	var data = viewBuildCtx.data;
	var isGeo = viewBuildCtx.isGeo;
	var isDataNaN = data && isNaN(data.get(data.mapDimension('value'), dataIdx));
	var itemLayout = data && data.getItemLayout(dataIdx); // In the following cases label will be drawn
	// 1. In map series and data value is NaN
	// 2. In geo component
	// 3. Region has no series legendIcon, which will be add a showLabel flag in mapSymbolLayout

	if (isGeo || isDataNaN || itemLayout && itemLayout.showLabel) {
		var query = !isGeo ? dataIdx : regionName;
		var labelFetcher = void 0; // Consider dataIdx not found.

		if (!data || dataIdx >= 0) {
			labelFetcher = mapOrGeoModel;
		}

		var specifiedTextOpt = labelXY ? {
			normal: {
				align: 'center',
				verticalAlign: 'middle'
			}
		} : null; // Caveat: must be called after `setDefaultStateProxy(el);` called.
		// because textContent will be assign with `el.stateProxy` inside.

		setLabelStyle(el, getLabelStatesModels(regionModel), {
			labelFetcher: labelFetcher,
			labelDataIndex: query,
			defaultText: regionName
		}, specifiedTextOpt);
		var textEl = el.getTextContent();

		if (textEl) {
			mapLabelRaw(textEl).ignore = textEl.ignore;

			if (el.textConfig && labelXY) {
				// Compute a relative offset based on the el bounding rect.
				var rect = el.getBoundingRect().clone(); // Need to make sure the percent position base on the same rect in normal and
				// emphasis state. Otherwise if using boundingRect of el, but the emphasis state
				// has borderWidth (even 0.5px), the text position will be changed obviously
				// if the position is very big like ['1234%', '1345%'].

				el.textConfig.layoutRect = rect;
				el.textConfig.position = [(labelXY[0] - rect.x) / rect.width * 100 + '%', (labelXY[1] - rect.y) / rect.height * 100 + '%'];
			}
		} // PENDING:
		// If labelLayout is enabled (test/label-layout.html), el.dataIndex should be specified.
		// But el.dataIndex is also used to determine whether user event should be triggered,
		// where el.seriesIndex or el.dataModel must be specified. At present for a single el
		// there is not case that "only label layout enabled but user event disabled", so here
		// we depends `resetEventTriggerForRegion` to do the job of setting `el.dataIndex`.

		el.disableLabelAnimation = true;
	} else {
		el.removeTextContent();
		el.removeTextConfig();
		el.disableLabelAnimation = null;
	}
}

function resetEventTriggerForRegion(viewBuildCtx, eventTrigger, regionName, regionModel, mapOrGeoModel, // Exist only if `viewBuildCtx.data` exists.
	dataIdx) {
	// setItemGraphicEl, setHoverStyle after all polygons and labels
	// are added to the regionGroup
	if (viewBuildCtx.data) {
		// FIXME: when series-map use a SVG map, and there are duplicated name specified
		// on different SVG elements, after `data.setItemGraphicEl(...)`:
		// (1) all of them will be mounted with `dataIndex`, `seriesIndex`, so that tooltip
		// can be triggered only mouse hover. That's correct.
		// (2) only the last element will be kept in `data`, so that if trigger tooltip
		// by `dispatchAction`, only the last one can be found and triggered. That might be
		// not correct. We will fix it in future if anyone demanding that.
		viewBuildCtx.data.setItemGraphicEl(dataIdx, eventTrigger);
	} // series-map will not trigger "geoselectchange" no matter it is
	// based on a declared geo component. Because series-map will
	// trigger "selectchange". If it trigger both the two events,
	// If users call `chart.dispatchAction({type: 'toggleSelect'})`,
	// it not easy to also fire event "geoselectchanged".
	else {
		// Package custom mouse event for geo component
		getECData(eventTrigger).eventData = {
			componentType: 'geo',
			componentIndex: mapOrGeoModel.componentIndex,
			geoIndex: mapOrGeoModel.componentIndex,
			name: regionName,
			region: regionModel && regionModel.option || {}
		};
	}
}

function resetTooltipForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel) {
	if (!viewBuildCtx.data) {
		graphic.setTooltipConfig({
			el: el,
			componentModel: mapOrGeoModel,
			itemName: regionName,
			// @ts-ignore FIXME:TS fix the "compatible with each other"?
			itemTooltipOption: regionModel.get('tooltip')
		});
	}
}

function resetStateTriggerForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel) {
	// @ts-ignore FIXME:TS fix the "compatible with each other"?
	el.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode'); // @ts-ignore FIXME:TS fix the "compatible with each other"?

	var emphasisModel = regionModel.getModel('emphasis');
	var focus = emphasisModel.get('focus');
	toggleHoverEmphasis(el, focus, emphasisModel.get('blurScope'), emphasisModel.get('disabled'));

	if (viewBuildCtx.isGeo) {
		enableComponentHighDownFeatures(el, mapOrGeoModel, regionName);
	}

	return focus;
}

function projectPolys(rings, // Polygons include exterior and interiors. Or polylines.
	createStream, isLine) {
	var polygons = [];
	var curPoly;

	function startPolygon() {
		curPoly = [];
	}

	function endPolygon() {
		if (curPoly.length) {
			polygons.push(curPoly);
			curPoly = [];
		}
	}

	var stream = createStream({
		polygonStart: startPolygon,
		polygonEnd: endPolygon,
		lineStart: startPolygon,
		lineEnd: endPolygon,
		point: function (x, y) {
			// May have NaN values from stream.
			if (isFinite(x) && isFinite(y)) {
				curPoly.push([x, y]);
			}
		},
		sphere: function () {}
	});
	!isLine && stream.polygonStart();
	zrUtil.each(rings, function (ring) {
		stream.lineStart();

		for (var i = 0; i < ring.length; i++) {
			stream.point(ring[i][0], ring[i][1]);
		}

		stream.lineEnd();
	});
	!isLine && stream.polygonEnd();
	return polygons;
}

export default MapDraw; // @ts-ignore FIXME:TS fix the "compatible with each other"?